package com.haha.redis.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author zouwz5
 * @date 2025/10/7 10:23
 */
@Component
@Slf4j
public final class RedisUtils {
    private static RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
        RedisUtils.redisTemplate = redisTemplate;
    }

    // ============================== Common 通用操作 ==============================

    /**
     * 设置缓存过期时间
     * @param key 键
     * @param time 时间(秒)
     * @return 是否设置成功
     */
    public static boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            log.error("设置缓存过期时间失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    /**
     * 获取key的剩余存活时间
     * @param key 键
     * @return 剩余时间(秒) -2表示key不存在，-1表示永久有效
     */
    public static long getExpire(String key) {
        Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        return expire != null ? expire : -2L;
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true存在 false不存在
     */
    public static boolean hasKey(String key) {
        try {
            Boolean hasKey = redisTemplate.hasKey(key);
            return Boolean.TRUE.equals(hasKey);
        } catch (Exception e) {
            log.error("判断key是否存在失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    /**
     * 删除缓存
     * @param keys 可以传一个或多个key
     */
    public static void del(String... keys) {
        if (keys != null && keys.length > 0) {
            if (keys.length == 1) {
                redisTemplate.delete(keys[0]);
            } else {
                redisTemplate.delete(Arrays.asList(keys));
            }
        }
    }

    // ============================== String 操作 ==============================

    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public static Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public static boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            log.error("设置缓存失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒)，time>0则设置，否则不设置
     * @return true成功 false失败
     */
    public static boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            log.error("设置缓存(含过期时间)失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return 递增后的值
     */
    public static long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        Long value = redisTemplate.opsForValue().increment(key, delta);
        return value != null ? value : 0L;
    }

    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return 递减后的值
     */
    public static long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        Long value = redisTemplate.opsForValue().increment(key, -delta);
        return value != null ? value : 0L;
    }

    // ============================== Hash 操作 ==============================

    /**
     * HashGet
     * @param key 键（不能为null）
     * @param item 项（不能为null）
     * @return 值
     */
    public static Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值:Map<HK, HV>，其中 HK通常是字段的类型（如 String），HV是值的类型（如 String, Object 等）。
     */
    public static Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true成功 false失败
     */
    public static boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            log.error("设置Hash缓存失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    /**
     * HashSet并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public static boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            log.error("设置Hash缓存(含过期时间)失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    /**
     * HashSet并设置具体的失效时间点
     * @param key 键
     * @param map 对应多个键值
     * @param time 失效日期（绝对时间点）
     * @return true成功 false失败
     */
    public static boolean hmset(String key, Map<String, Object> map, Date time) {
        try {
            // 参数校验
            if (key == null || key.trim().isEmpty()) {
                log.error("设置Hash缓存失败: key不能为空");
                return false;
            }
            if (map == null || map.isEmpty()) {
                log.error("设置Hash缓存失败: map数据不能为空");
                return false;
            }
            if (time == null) {
                log.error("设置Hash缓存失败: 失效时间不能为空");
                return false;
            }

            // 检查失效时间是否有效（必须是将来的时间）
            if (time.before(new Date())) {
                log.error("设置Hash缓存失败: 失效时间必须是将来的时间");
                return false;
            }

            // 设置Hash数据
            redisTemplate.opsForHash().putAll(key, map);

            // 设置具体的过期时间点
            boolean expireResult = redisTemplate.expireAt(key, time);

            if (!expireResult) {
                log.warn("设置Hash缓存过期时间可能未生效, key: {}, time: {}", key, time);
            }

            log.debug("设置Hash缓存成功, key: {}, 数据量: {}, 失效时间: {}",
                    key, map.size(), time);
            return true;

        } catch (Exception e) {
            log.error("设置Hash缓存(含失效时间)失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据，如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true成功 false失败
     */
    public static boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            log.error("设置Hash表数据失败 key: {}, item: {}, error: {}", key, item, e.getMessage());
            return false;
        }
    }

    /**
     * 向Hash表放入数据并设置绝对过期时间点
     * @param key Redis键
     * @param item Hash字段
     * @param value 字段值
     * @param expireTime 失效时间（绝对时间点）
     * @return true-成功 false-失败
     */
    public static boolean hset(String key, String item, Object value, Date expireTime) {
        // 参数校验
        if (key == null || key.trim().isEmpty()) {
            log.error("设置Hash字段失败: key不能为空");
            return false;
        }
        if (item == null || item.trim().isEmpty()) {
            log.error("设置Hash字段失败: item字段不能为空");
            return false;
        }
        if (value == null) {
            log.error("设置Hash字段失败: value值不能为空");
            return false;
        }
        if (expireTime == null) {
            log.error("设置Hash字段失败: 失效时间不能为空");
            return false;
        }

        // 检查失效时间是否有效（必须是将来的时间）
        if (expireTime.before(new Date())) {
            log.error("设置Hash字段失败: 失效时间必须是将来的时间");
            return false;
        }

        try {
            // 1. 设置Hash字段值[2,3](@ref)
            redisTemplate.opsForHash().put(key, item, value);

            // 2. 设置绝对过期时间点[2,5](@ref)
            Boolean expireResult = redisTemplate.expireAt(key, expireTime);

            if (Boolean.TRUE.equals(expireResult)) {
                log.debug("设置Hash字段成功, key: {}, field: {}, 过期时间: {}",
                        key, item, expireTime);
                return true;
            } else {
                log.error("设置Hash字段过期时间失败, key: {}", key);
                // 过期时间设置失败时，删除已设置的字段，保证数据一致性
                redisTemplate.opsForHash().delete(key, item);
                return false;
            }

        } catch (Exception e) {
            log.error("设置Hash字段(含过期时间)失败 key: {}, field: {}, error: {}",
                    key, item, e.getMessage());
            return false;
        }
    }

    /**
     * 删除hash表中的值
     * @param key 键（不能为null）
     * @param item 项（可以使多个，不能为null）
     */
    public static void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     * @param key 键（不能为null）
     * @param item 项（不能为null）
     * @return true存在 false不存在
     */
    public static boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    // ============================== List 操作 ==============================

    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始索引
     * @param end 结束索引（0到-1代表所有值）
     * @return 列表内容
     */
    public static List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            log.error("获取List缓存失败 key: {}, error: {}", key, e.getMessage());
            return null;
        }
    }

    /**
     * 将list放入缓存（右插）
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public static boolean rSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            log.error("设置List缓存(右插)失败 key: {}, error: {}", key, e.getMessage());
            return false;
        }
    }

    // ============================== 分布式锁（进阶功能） ==============================

    /**
     * 尝试获取分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识（可使用UUID），用于释放锁时验证
     * @param expireTime 锁的过期时间(秒)
     * @return 是否获取成功
     */
    public static boolean tryLock(String lockKey, String requestId, long expireTime) {
        try {
            Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            log.error("获取分布式锁失败 lockKey: {}, error: {}", lockKey, e.getMessage());
            return false;
        }
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseLock(String lockKey, String requestId) {
        // 使用Lua脚本保证原子性：判断锁的值是否为当前请求ID，是则删除
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(luaScript);
        redisScript.setResultType(Long.class);

        Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
        return result != null && result == 1;
    }
}
